////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) Roman Ranzmaier (http://www.ranzmaier.at/contact.aspx).  All rights reserved.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

using System;
using Microsoft.SPOT;
using System.IO;
using System.Text;

namespace Ranzmaier.NETMF.IO
{
    /// <summary>
    /// Writes primitive types in binary to a stream
    /// his class corresponds to the .net class BinaryWriter 
    /// Strings are written in UTF8 encoding 
    /// NOTE: .net Micro framework only supports UTF8 Encoding
    /// the type <see cref="System.Decimal">System.Decimal</see> is not supported because my (and maybe all??) hardware does not support it.
    /// </summary>
    public class BinaryWriter : IDisposable
    {
        /// <summary>
        /// Holds the underlying stream.
        /// </summary>
        protected Stream _stream;
        private byte[] _buffer = new byte[8];

        /// <summary>
        /// Initializes a new instance of the <see cref="BinaryWriter"/> class based on the supplied stream and using UTF-8 as the encoding for strings.
        /// </summary>
        /// <param name="stream">The stream.</param>
        /// <exception cref="T:System.ArgumentException">The stream does not support writing, or the stream is already closed. </exception>
        ///   
        /// <exception cref="T:System.ArgumentNullException">
        ///   <paramref name="stream"/> is null. </exception>
        public BinaryWriter(Stream stream)
        {
            if (stream == null)
            {
                throw new ArgumentNullException("output");
            }
            if (!stream.CanWrite)
            {
                throw new ArgumentException("Stream not writeable");
            }
            _stream = stream;
        }

        /// <summary>
        /// Gets the underlying stream of the <see cref="BinaryWriter"/>.
        /// </summary>
        /// <returns>The underlying stream associated with the BinaryWriter.</returns>
        public Stream BaseStream
        {
            get
            {
                Flush();
                return _stream;
            }
        }

        /// <summary>
        /// Closes the current <see cref="BinaryWriter"/> and the underlying stream.
        /// </summary>
        public virtual void Close()
        {
            this.Dispose();
        }

        /// <summary>
        /// Clears all buffers for the current writer and causes any buffered data to be written to the underlying device.
        /// </summary>
        public virtual void Flush()
        {
            _stream.Flush();
        }
        /// <summary>
        /// Sets the position within the current stream.
        /// </summary>
        /// <param name="offset">A byte offset relative to <paramref name="origin"/>.</param>
        /// <param name="origin">A field of <see cref="T:System.IO.SeekOrigin"/> indicating the reference point from which the new position is to be obtained.</param>
        /// <returns>
        /// The position with the current stream.
        /// </returns>
        /// <exception cref="T:System.IO.IOException">The file pointer was moved to an invalid location. </exception>
        ///   
        /// <exception cref="T:System.ArgumentException">The <see cref="T:System.IO.SeekOrigin"/> value is invalid. </exception>
        public virtual long Seek(int offset, SeekOrigin origin)
        {
            return _stream.Seek((long)offset, origin);
        }
        /// <summary>Writes a one-byte Boolean value to the current stream, with 0 representing false and 1 representing true.</summary>
        /// <param name="value">The Boolean value to write (0 or 1). </param>
        /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
        /// <exception cref="T:System.ObjectDisposedException">The stream is closed. </exception>
        public virtual void Write(bool value)
        {
            _buffer[0] = (byte)(value ? 1 : 0);
            _stream.Write(this._buffer, 0, 1);
        }
        /// <summary>
        /// Writes an unsigned byte to the current stream and advances the stream position by one byte.
        /// </summary>
        /// <param name="value">The unsigned byte to write.</param>
        /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
        ///   
        /// <exception cref="T:System.ObjectDisposedException">The stream is closed. </exception>
        public virtual void Write(byte value)
        {
            _stream.WriteByte(value);

        }
        /// <summary>
        /// Writes a byte array to the underlying stream.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
        ///   
        /// <exception cref="T:System.ObjectDisposedException">The stream is closed. </exception>
        ///   
        /// <exception cref="T:System.ArgumentNullException">
        ///   <paramref name="value"/> is null. </exception>
        public virtual void Write(byte[] value)
        {
            _stream.Write(value, 0, value.Length);
        }
        /// <summary>
        /// Writes a Unicode character to the current stream and advances the current position of the stream in accordance with the Encoding used and the specific characters being written to the stream.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
        ///   
        /// <exception cref="T:System.ObjectDisposedException">The stream is closed. </exception>
        ///   
        /// <exception cref="T:System.ArgumentException">
        ///   <paramref name="value"/> is a single surrogate character.</exception>
        public virtual void Write(char value)
        {
            _stream.WriteByte((byte)value);
        }
        /// <summary>
        /// Writes a character array to the current stream and advances the current position of the stream in accordance with the Encoding used and the specific characters being written to the stream.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <exception cref="T:System.ArgumentNullException">
        ///   <paramref name="value"/> is null. </exception>
        ///   
        /// <exception cref="T:System.ObjectDisposedException">The stream is closed. </exception>
        ///   
        /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>

        public virtual void Write(char[] value)
        {
            byte[] v = Encoding.UTF8.GetBytes(new string(value));
            _stream.Write(v, 0, v.Length);
        }
        /// <summary>
        /// Writes an eight-byte floating-point value to the current stream and advances the stream position by eight bytes.
        /// </summary>
        /// <param name="value">The eight-byte floating-point value to write.</param>
        /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
        ///   
        /// <exception cref="T:System.ObjectDisposedException">The stream is closed. </exception>
        public virtual void Write(double value)
        {
            var bytes = Reflection.Serialize(value, typeof(double));
            for (int i = 1; i <= 8; i++)
                _buffer[8 - i] = bytes[i];
            _stream.Write(_buffer, 0, 8);

        }
        /// <summary>
        /// Writes a four-byte floating-point value to the current stream and advances the stream position by four bytes.
        /// </summary>
        /// <param name="value">The four-byte floating-point value to write.</param>
        /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
        ///   
        /// <exception cref="T:System.ObjectDisposedException">The stream is closed. </exception>
        public virtual void Write(float value)
        {
            var bytes = Reflection.Serialize(value, typeof(float));
            for (int i = 1; i <= 4; i++)
                _buffer[4 - i] = bytes[i];
            _stream.Write(_buffer, 0, 4);

        }
        /// <summary>
        /// Writes a four-byte signed integer to the current stream and advances the stream position by four bytes.
        /// </summary>
        /// <param name="value">The four-byte signed integer to write.</param>
        /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
        ///   
        /// <exception cref="T:System.ObjectDisposedException">The stream is closed. </exception>
	
        public void Write(int value)
        {
            _buffer[0] = (byte)(value);
            _buffer[1] = (byte)(value >> 8 & 0xff);
            _buffer[2] = (byte)(value >> 16 & 0xff);
            _buffer[3] = (byte)(value >> 24 & 0xff);
            _stream.Write(_buffer, 0, 4);
        }
        /// <summary>
        /// Writes an eight-byte signed integer to the current stream and advances the stream position by eight bytes.
        /// </summary>
        /// <param name="value">The eight-byte signed integer to write.</param>
        /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
        ///   
        /// <exception cref="T:System.ObjectDisposedException">The stream is closed. </exception>
        public virtual void Write(long value)
        {
            _buffer[0] = (byte)(value);
            _buffer[1] = (byte)(value >> 8 & 0xff);
            _buffer[2] = (byte)(value >> 16 & 0xff);
            _buffer[3] = (byte)(value >> 24 & 0xff);
            _buffer[4] = (byte)(value >> 32 & 0xff);
            _buffer[5] = (byte)(value >> 40 & 0xff);
            _buffer[6] = (byte)(value >> 48 & 0xff);
            _buffer[7] = (byte)(value >> 56 & 0xff);
            _stream.Write(_buffer, 0, 8);

        }
        /// <summary>
        /// Writes a signed byte to the current stream and advances the stream position by one byte.
        /// </summary>
        /// <param name="value">The signed byte to write.</param>
        /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
        ///   
        /// <exception cref="T:System.ObjectDisposedException">The stream is closed. </exception>
        public virtual void Write(sbyte value)
        {
            _stream.WriteByte((byte)value);
        }
        /// <summary>
        /// Writes a two-byte signed integer to the current stream and advances the stream position by two bytes.
        /// </summary>
        /// <param name="value">The two-byte signed integer to write.</param>
        /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
        ///   
        /// <exception cref="T:System.ObjectDisposedException">The stream is closed. </exception>
        public virtual void Write(short value)
        {
            _buffer[0] = (byte)(value);
            _buffer[1] = (byte)(value >> 8 & 0xff);
            _stream.Write(_buffer, 0, 2);
        }
        /// <summary>
        /// Writes a length-prefixed string to this stream in the current encoding of the <see cref="T:System.IO.BinaryWriter"/>, and advances the current position of the stream in accordance with the encoding used and the specific characters being written to the stream.
        /// </summary>
        /// <param name="value">The value to write.</param>
        /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
        ///   
        /// <exception cref="T:System.ArgumentNullException">
        ///   <paramref name="value"/> is null. </exception>
        ///   
        /// <exception cref="T:System.ObjectDisposedException">The stream is closed. </exception>
        public virtual void Write(string value)
        {
            byte[] strData = Encoding.UTF8.GetBytes(value);
            byte[] len = new byte[4]; // Estimated maximum string lenght 2113664 bytes 
            byte first = 0;
            int lenght = value.Length;
            for (short i = 3; i >= 0; i--)
            {
                double f = System.Math.Pow(128, i);
                if (lenght / f > 1)
                {
                    if (first < i)
                        first = (byte)i;
                    len[i] = (byte)(lenght / f + (first == i ? 0 : 128));
                    lenght -= (int)len[i] * (int)f;
                }
            }
            _stream.Write(len, 0, first + 1);
            _stream.Write(strData, 0, strData.Length);
        }
        /// <summary>
        /// Writes a four-byte unsigned integer to the current stream and advances the stream position by four bytes.
        /// </summary>
        /// <param name="value">The four-byte unsigned integer to write.</param>
        /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
        ///   
        /// <exception cref="T:System.ObjectDisposedException">The stream is closed. </exception>
        public virtual void Write(uint value)
        {
            _buffer[0] = (byte)(value);
            _buffer[1] = (byte)(value >> 8 & 0xff);
            _buffer[2] = (byte)(value >> 16 & 0xff);
            _buffer[3] = (byte)(value >> 24 & 0xff);

            _stream.Write(_buffer, 0, 4);
        }
        /// <summary>
        /// Writes an eight-byte unsigned integer to the current stream and advances the stream position by eight bytes.
        /// </summary>
        /// <param name="value">The eight-byte unsigned integer to write.</param>
        /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
        ///   
        /// <exception cref="T:System.ObjectDisposedException">The stream is closed. </exception>
        public virtual void Write(ulong value)
        {
            _buffer[0] = (byte)(value);
            _buffer[1] = (byte)(value >> 8 & 0xff);
            _buffer[2] = (byte)(value >> 16 & 0xff);
            _buffer[3] = (byte)(value >> 24 & 0xff);
            _buffer[4] = (byte)(value >> 32 & 0xff);
            _buffer[5] = (byte)(value >> 40 & 0xff);
            _buffer[6] = (byte)(value >> 48 & 0xff);
            _buffer[7] = (byte)(value >> 56 & 0xff);
            _stream.Write(_buffer, 0, 8);
        }
        /// <summary>
        /// Writes a two-byte unsigned integer to the current stream and advances the stream position by two bytes.
        /// </summary>
        /// <param name="value">The two-byte unsigned integer to write.</param>
        /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
        ///   
        /// <exception cref="T:System.ObjectDisposedException">The stream is closed. </exception>
        public virtual void Write(ushort value)
        {
            _buffer[0] = (byte)(value);
            _buffer[1] = (byte)(value >> 8 & 0xff);
            _stream.Write(_buffer, 0, 2);
        }
        /// <summary>
        /// Writes a region of a byte array to the current stream.
        /// </summary>
        /// <param name="buffer">The value.</param>
        /// <param name="index">The starting point in <paramref name="buffer"/> at which to begin writing.</param>
        /// <param name="count">The number of bytes to write.</param>
        /// <exception cref="T:System.ArgumentException">The buffer length minus <paramref name="index"/> is less than <paramref name="count"/>. </exception>
        ///   
        /// <exception cref="T:System.ArgumentNullException">
        ///   <paramref name="buffer"/> is null. </exception>
        ///   
        /// <exception cref="T:System.ArgumentOutOfRangeException">
        ///   <paramref name="index"/> or <paramref name="count"/> is negative. </exception>
        ///   
        /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
        ///   
        /// <exception cref="T:System.ObjectDisposedException">The stream is closed. </exception>
        public virtual void Write(byte[] buffer, int index, int count)
        {
            _stream.Write(buffer, index, count);
        }
        /// <summary>
        /// Writes a section of a character array to the current stream, and advances the current position of the stream in accordance with the Encoding used and perhaps the specific characters being written to the stream.
        /// </summary>
        /// <param name="buffer">The value.</param>
        /// <param name="index">The starting point in <paramref name="buffer"/> from which to begin writing.</param>
        /// <param name="count">The number of characters to write.</param>
        /// <exception cref="T:System.ArgumentException">The buffer length minus <paramref name="index"/> is less than <paramref name="count"/>. </exception>
        ///   
        /// <exception cref="T:System.ArgumentNullException">
        ///   <paramref name="buffer"/> is null. </exception>
        ///   
        /// <exception cref="T:System.ArgumentOutOfRangeException">
        ///   <paramref name="index"/> or <paramref name="count"/> is negative. </exception>
        ///   
        /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
        ///   
        /// <exception cref="T:System.ObjectDisposedException">The stream is closed. </exception>
        public virtual void Write(char[] buffer, int index, int count)
        {
            byte[] v = Encoding.UTF8.GetBytes(new string(buffer));
            _stream.Write(v, index, count);
        }


        /// <summary>
        /// Releases all resources used by the current instance of the <see cref="BinaryWriter"/> class.
        /// </summary>
        public void Dispose()
        {
            if (_stream != null)
                _stream.Close();
            _stream = null;

        }
    }
}
